#include <cstdlib>
#include <iostream>
#include <string.h>
#include <sys/stat.h>
#include <vector>
#include <fstream>
#include <sstream>
#include <time.h>
#include <math.h>
#include <list>


typedef struct _event_t
{
   double arrivalTime;
   double serviceTime;
   double departureTime;
   double HRR;
   
} event_t;

typedef std::list<event_t> eventQueue_t;

typedef struct _metrics_t
{
	double avgTurnaround;
    double throughPut;
    double avgProcessInQueue;
    double cpuUtilization;
} metrics_t;

//Function Prototypes
void eventQueue_create(eventQueue_t&, double, double);
bool argParser( int argc, char *argv[], double&, int&, double&, double& );
void firstComeFirstServe(eventQueue_t&, metrics_t&);
void shortestJobFirst(eventQueue_t&, metrics_t&);
void highestResponseRatioNext(eventQueue_t&, metrics_t&);
void shortestRemainingTimeFirst(eventQueue_t&, metrics_t&);
void roundRobin(eventQueue_t&, double, metrics_t&);
bool compareServiceTime(event_t, event_t);
bool compareHRR(event_t, event_t);
bool compareArrivalTime(event_t, event_t);
void computeMetrics(metrics_t&, eventQueue_t&, double&, double&, int&, int&);

//NOT WORKING just putting down ideas
int main( int argc, char *argv[] )
{
    double lambda = 5;
	int scheduleType = 1;
	double serviceTime = 0.6;
	double quantum = 1;
	
	metrics_t metrics;
	metrics.avgProcessInQueue = 0;
	metrics.avgTurnaround = 0;
	metrics.cpuUtilization = 0;
	metrics.throughPut = 0;

    if(!argParser( argc, argv, lambda, scheduleType, serviceTime, quantum ))
    {
    	return 1;
    }

    eventQueue_t eventQueue;
    eventQueue_create(eventQueue, lambda, serviceTime);

#ifdef DEBUG
    double latestArrivalTime = 0;
    double totalServiceTime = 0;
    for(eventQueue_t::iterator event = eventQueue.begin(); event != eventQueue.end(); ++event)
    {
    	// If this event's arrival time is later, remember it instead of the previous
    	latestArrivalTime = event->arrivalTime > latestArrivalTime?
    			event->arrivalTime : latestArrivalTime;
    	totalServiceTime += event->serviceTime;
    }
    std::cout << eventQueue.size() << " events queued" << std::endl;
    std::cout << "Average arrival time: " << (((double) eventQueue.size()) / latestArrivalTime) << std::endl;
    std::cout << "Average service time: " << (totalServiceTime / eventQueue.size()) << std::endl;
#endif

   	switch( scheduleType )
   	{
            case 1:
                 firstComeFirstServe(eventQueue, metrics);
                 break;
            case 2:
                 shortestJobFirst(eventQueue, metrics);
                 break;
            case 3:
                 highestResponseRatioNext(eventQueue, metrics);
                 break;
            case 4:
                 shortestRemainingTimeFirst(eventQueue, metrics);
                 break;
            case 5:
                 roundRobin(eventQueue, quantum, metrics);
                 break;
            default:
            	 std::cout << "Hmmm... my arg checker missed something. I just don't understand." << std::endl;
                 std::cout << "The number of queue theory was not in the list provided" << std::endl; 
				 break;
    }
   	
   	std::cout << scheduleType << ","
   	          << lambda << ","
              << quantum << ","
   	          << metrics.avgProcessInQueue << ","
   	          << metrics.avgTurnaround << ","
   	          << metrics.cpuUtilization << ","
   	          << metrics.throughPut << std::endl ;
   	          
    
}

//NOT Tested but the idea is complete
void eventQueue_create(eventQueue_t &eventQueue, double averageLamda, double averageServiceTime)
{
     double tempPerviousTime = 0;
     double tempArrivalTime = 0;
     double tempServiceTime = 0;
     srand ( time(NULL) );
     
     for (int i = 0; i < 10000; i++)
     {
         //The new event being created
         event_t temp;
         
         //Calculating arrivalTime for new event
         double temp4Arrival = (double) (rand() % 99 + 1);
         double u = temp4Arrival / 100.0;
         tempArrivalTime = (-1 / averageLamda) * log(1 - u);
         
         //Calculated arrivalTime + offset(last arrivalTime) = new arrivalTime
         tempArrivalTime += tempPerviousTime;                                       
         
         //offset for next arrivalTime
         tempPerviousTime = tempArrivalTime;
         
         //Calculating serviceTime for event
         double temp4Service = (double) (rand() % 99 + 1);
         double v = temp4Service / 100.0;
         tempServiceTime = -1 / (1 / averageServiceTime) * log(1 - v);
         
         temp.arrivalTime = tempArrivalTime;
         temp.serviceTime = tempServiceTime;
         temp.departureTime = 0;
         temp.HRR = 0;
         eventQueue.push_front(temp);
     }
}

//NOT Tested but the idea is complete
//Test for the queueing theory First Come First Serve. 
void firstComeFirstServe(eventQueue_t &eventQueue, metrics_t &metrics)
{
     eventQueue_t readyQueue;
     double timer = 0;
     double idleTime = 0;
     double busyTime = 0;
     int numOfEvents = 0;
     int eventDone = 0;
     int readyQueueNum = 0;
     int readyQueuePings = 0;
     event_t worker;
     event_t tester;
     event_t front;
     eventQueue_t doneQueue;
     eventQueue_t::reverse_iterator it1;
     
     //Setting up iterators and testing pointer
     tester = eventQueue.back();
     it1 = eventQueue.rbegin();
     timer = tester.arrivalTime;
     idleTime = timer;
     
     //Loops till all events are completed
     while(eventDone != eventQueue.size())
     {
         //Pulls any events that need to be added to the ready queue.
         while( (tester.arrivalTime <= timer) && (numOfEvents != eventQueue.size()) )
         {
            readyQueue.push_front(tester);
            it1++;
            tester = *it1;
            numOfEvents++;
         }
         
         //Keeping track of number of processes in the ready queue.
         readyQueuePings++;
         readyQueueNum += readyQueue.size();
         
         //Does the event adds service time of event to timer and adds departure time to 
         //event struct and places event from readyQ to doneQ.
         if(readyQueue.size() != 0)
         {
            worker = readyQueue.back();
            timer += worker.serviceTime;
            busyTime += worker.serviceTime;
            worker.departureTime = timer;
            doneQueue.push_front(worker);
            readyQueue.pop_back();
            eventDone++;
         }
         
         //Fast-forward to the next event
         if(readyQueue.size() == 0 && it1 != eventQueue.rend())
         {
        	 idleTime += tester.arrivalTime - timer;
        	 timer = tester.arrivalTime;
         }
     }

     computeMetrics(metrics, doneQueue, timer, busyTime, readyQueueNum, readyQueuePings);
}

//NOT Tested but the idea is complete
//Test for the queueing theory Shortest Job First.
void shortestJobFirst(eventQueue_t& eventQueue, metrics_t &metrics)
{
     double timer = 0;
     double idleTime = 0;
     double busyTime = 0;
     int numOfEvents = 0;
     int eventDone = 0;
     int readyQueueNum = 0;
     int readyQueuePings = 0;
     event_t worker;
     event_t tester;
     event_t front;
     eventQueue_t readyQueue;
     eventQueue_t doneQueue;
     eventQueue_t::reverse_iterator it1;
     
     //Setting up iterators and testing pointer
     tester = eventQueue.back();
     it1 = eventQueue.rbegin();
     timer = tester.arrivalTime;
     idleTime = timer;
     
     //Loops till all events are completed
     while(eventDone != eventQueue.size())
     {
         //Pulls any events that need to be added to the ready queue.
         while( (tester.arrivalTime <= timer) && (numOfEvents != eventQueue.size()) )
         {
            readyQueue.push_front(tester);
            it1++;
            tester = *it1;
            numOfEvents++;
         }
         
         //Sorting ReadyQueue by Serive time
         readyQueue.sort(compareServiceTime);
         
         //Keeping track of number of processes in the ready queue.
         readyQueuePings++;
         readyQueueNum += readyQueue.size();
         
         //Does the event adds service time of event to timer and adds departure time to 
         //event struct and places event from readyQ to doneQ.
         if(readyQueue.size() != 0)
         {
            worker = readyQueue.front();
            timer += worker.serviceTime;
            busyTime += worker.serviceTime;
            worker.departureTime = timer;
            doneQueue.push_front(worker);
            readyQueue.pop_front();
            eventDone++;
         }
         
         //Changes idle if there is something or nothing in the Queue
         if(readyQueue.size() == 0 && it1 != eventQueue.rend())
         {
        	 idleTime += tester.arrivalTime - timer;
        	 timer = tester.arrivalTime;
         } 
     }

     computeMetrics(metrics, doneQueue, timer, busyTime, readyQueueNum, readyQueuePings);
}

//NOT Tested but the idea is complete
//Test for the queueing theory Highest Response Ratio Next. 
void highestResponseRatioNext(eventQueue_t &eventQueue, metrics_t &metrics)
{
     eventQueue_t readyQueue;
     double timer = 0;
     double idleTime = 0;
     double busyTime = 0;
     int numOfEvents = 0;
     int eventDone = 0;
     int readyQueueNum = 0;
     int readyQueuePings = 0;
     event_t worker;
     event_t tester;
     event_t front;
     event_t nextFront;
     event_t calHRR;
     eventQueue_t doneQueue;
     eventQueue_t::reverse_iterator it1;
     eventQueue_t::iterator it2;
     
     //Setting up iterators and testing pointer
     tester = eventQueue.back();
     it1 = eventQueue.rbegin();
     timer = tester.arrivalTime;
     
     //Loops till all events are completed
     while(eventDone != eventQueue.size())
     {
         //Pulls any events that need to be added to the ready queue.
         while( (tester.arrivalTime <= timer) && (numOfEvents != eventQueue.size()) )
         {
            readyQueue.push_front(tester);
            it1++;
            tester = *it1;
            numOfEvents++;
         }
         
         //Keeping track of number of processes in the ready queue.
         readyQueuePings++;
         readyQueueNum += readyQueue.size();
         
         //Calculate Response Ratios for everything in the readyQueue.
         for (it2 = readyQueue.begin(); it2 != readyQueue.end(); ++it2)
         {
            calHRR = *it2;
            calHRR.HRR = ((timer - calHRR.arrivalTime) + calHRR.serviceTime) / calHRR.serviceTime; 
         }
              
         //Sorting ReadyQueue by Response Ratio
         readyQueue.sort(compareHRR);     
                  
         //Does the event adds service time of event to timer and adds departure time to 
         //event struct and places event from readyQ to doneQ.
         if(readyQueue.size() != 0)
         {
            worker = readyQueue.front();
            timer += worker.serviceTime;
            busyTime += worker.serviceTime;
            worker.departureTime = timer;
            doneQueue.push_front(worker);
            readyQueue.pop_front();
            eventDone++;
         }
         
         //Changes idle if there is something or nothing in the Queue
         if(readyQueue.size() == 0 && it1 != eventQueue.rend())
         {
        	 idleTime += tester.arrivalTime - timer;
        	 timer = tester.arrivalTime;
         } 
     }

     computeMetrics(metrics, doneQueue, timer, busyTime, readyQueueNum, readyQueuePings);
}

//NOT Tested but the idea is complete
//Test for the queueing theory Shortest Remaining Time First. 
void shortestRemainingTimeFirst(eventQueue_t &eventQueue, metrics_t &metrics)
{
     eventQueue_t readyQueue;
     double timer = 0;
     double idleTime = 0;
     double busyTime = 0;
     int numOfEvents = 0;
     int eventDone = 0;
     int readyQueueNum = 0;
     int readyQueuePings = 0;
     event_t worker;
     event_t tester;
     event_t front;
     eventQueue_t doneQueue;
     eventQueue_t::reverse_iterator it1;
     
     //Setting up iterators and testing pointer
     tester = eventQueue.back();
     it1 = eventQueue.rbegin();
     timer = tester.arrivalTime;
     idleTime = timer;
     
     //Loops till all events are completed
     while(eventDone != eventQueue.size())
     {
         //Pulls any events that need to be added to the ready queue.
         while( (tester.arrivalTime <= timer) && (numOfEvents != eventQueue.size()) )
         {
            readyQueue.push_front(tester);
            it1++;
            tester = *it1;
            numOfEvents++;
         }
         
         //Keeping track of number of processes in the ready queue.
         readyQueuePings++;
         readyQueueNum += readyQueue.size();
                      
         //Sorting ReadyQueue by Response Ratio
         readyQueue.sort(compareServiceTime);     
                  
         //Does the event adds service time of event to timer and adds departure time to 
         //event struct and places event from readyQ to doneQ.
         if(readyQueue.size() != 0)
         {
            worker = readyQueue.front();
            //If the current event's serviceTime is less than the ArrivalTime of the 
            //nextFront then event done.
            if(numOfEvents == eventQueue.size() || timer + worker.serviceTime <= tester.arrivalTime)
            {
               timer += worker.serviceTime;
               busyTime += worker.serviceTime;
               worker.departureTime = timer;
               doneQueue.push_front(worker);
               readyQueue.pop_front();
               eventDone++;
            }
            //If the serviceTime of the current event is longer then the time before
            //the next arrivalTime then just inc timer,busyTime,and current
            //serviceTime by the different of timer and arrivalTime and 
            //go on to arrival.
            else
            {
               double timeUntilNextArrival = tester.arrivalTime - timer;
               timer += timeUntilNextArrival;
               busyTime += timeUntilNextArrival;
               worker.serviceTime -= timeUntilNextArrival;
               readyQueue.pop_front();
               readyQueue.push_back(worker);
            }
         }
         
         //Changes idle if there is something or nothing in the Queue
         if(readyQueue.size() == 0 && it1 != eventQueue.rend())
         {
        	 idleTime += tester.arrivalTime - timer;
        	 timer = tester.arrivalTime;
         } 
     }

     computeMetrics(metrics, doneQueue, timer, busyTime, readyQueueNum, readyQueuePings);
}

//NOT DONE
//Test for the queueing theory Round Robin. 
void roundRobin(eventQueue_t &eventQueue, double quantum, metrics_t &metrics)
{
     eventQueue_t readyQueue;
     double timer = 0;
     double idleTime = 0;
     double busyTime = 0;
#ifdef BEFORE_RESTRUCTURING
     double remainingQuantum = 0;
#else
     double nextPreemption = timer + quantum;
#endif
     int numOfEvents = 0;
     int eventDone = 0;
     int readyQueueNum = 0;
     int readyQueuePings = 0;
     event_t worker;
     event_t tester;
     event_t front;
     eventQueue_t doneQueue;
     eventQueue_t::reverse_iterator it1;
     
     //Setting up iterators and testing pointer
     tester = eventQueue.back();
     it1 = eventQueue.rbegin();
     timer = tester.arrivalTime;
     idleTime = timer;
     
     //Loops till all events are completed
     while(eventDone != eventQueue.size())
     {
         //Pulls any events that need to be added to the ready queue.
         while( (tester.arrivalTime <= timer) && (numOfEvents != eventQueue.size()) )
         {
            readyQueue.push_front(tester);
            it1++;
            tester = *it1;
            numOfEvents++;
         }
         
         //Keeping track of number of processes in the ready queue.
         readyQueuePings++;
         readyQueueNum += readyQueue.size();
                                       
         //Does the event, adds service time of event to timer, adds departure time to 
         //event struct, and places event from readyQ to doneQ.
         if(readyQueue.size() != 0)
         {
            worker = readyQueue.back();
            //If the current event's serviceTime is less than the quantum then
            //the current event will complete.
            //Assume an event will finish if its remaining service time exactly
            //coincides with the next quantum.
#ifdef BEFORE_RESTRUCTURING
            if(worker.serviceTime <= quantum)
            {
               timer += worker.serviceTime;
               busyTime += worker.serviceTime;
               worker.departureTime = timer;
               remainingQuantum = quantum - worker.serviceTime;
               doneQueue.push_front(worker);
               eventDone++;
               
               //While there is still time in the Quantum and there is still
               //things in the readyQueue then start working on the next event.
               while(readyQueue.size() != 0 && remainingQuantum <= 0)
               {
                  worker = readyQueue.back();
                  if(worker.serviceTime < remainingQuantum)
                  {
                     timer += worker.serviceTime;
                     busyTime += worker.serviceTime;
                     worker.departureTime = timer;
                     remainingQuantum = quantum - worker.serviceTimer;
                     doneQueue.push_front(readyQueue.pop_back());
                     eventDone++;
                  }
                  else
                  {
                      timer += quantum;
                      busyTime += quantum;
                      worker.serviceTime = worker.serviceTime - quantum;
                  }
               }
            }
            //If the serviceTime of the current event is longer then the quantum
            //then the timer,busyTime,and the current working event's serviceTime
            //will be incremented by the quantum.
            else
            {
               timer += quantum;
               busyTime += quantum;
               worker.serviceTime = worker.serviceTime - quantum;
            }            
#else // AFTER_RESTRUCTURING
            // Schedule the next forced preemption
            while(nextPreemption <= timer)
            	nextPreemption += quantum;

            if(timer + worker.serviceTime <= nextPreemption)
            {
               busyTime += worker.serviceTime;
               timer += worker.serviceTime;
               // Finish the event and move it to the done queue
               worker.departureTime = timer;
               doneQueue.push_front(worker);
               readyQueue.pop_back();
               eventDone++;
            }
            else
            {
               busyTime += nextPreemption - timer;
               worker.serviceTime -= (nextPreemption - timer);
               timer = nextPreemption;
               // Rotate the ready queue
               readyQueue.push_front(worker);
               readyQueue.pop_back();
            }
 
#endif // END_OF_RESTRUCTURING
         }
         
         //Changes idle if there is something or nothing in the Queue
         if(readyQueue.size() == 0 && it1 != eventQueue.rend())
         {
        	 idleTime += tester.arrivalTime - timer;
        	 timer = tester.arrivalTime;
         } 
     }

     computeMetrics(metrics, doneQueue, timer, busyTime, readyQueueNum, readyQueuePings);
}

//Used in shortest remaining service time and shortest job first
//Used in sort function 
//Tells the sort function that if the service time of the first event is shorter
//than the service of the second event then the first event should go first when
//sorting.
bool compareServiceTime(event_t first, event_t second)
{
#ifdef WHAT_THE_HELL_MAN		// :)
     if(first.serviceTime < second.serviceTime)
     {
        return true;
     }
     else
     {
        return false;
     }
#else
     return first.serviceTime < second.serviceTime;
#endif
}

//Used in highest response ratio first
//Used in sort function 
//Tells the sort function that if the HRR of the first event is shorter
//than the HRR of the second event then the first event should go first when
//sorting.
bool compareHRR(event_t first, event_t second)
{
#ifdef WHAT_THE_HELL_MAN		// :)
     if(first.HRR < second.HRR)
     {
        return true;
     }
     else
     {
        return false;
     }
#else
     return first.HRR < second.HRR;
#endif
}

//Not used becuase eventQueue/readyQueue was pre sorted so there was not need 
//to sort the queue's for First come first service 
bool compareArrivalTime(event_t first, event_t second)
{
#ifdef WHAT_THE_HELL_MAN		// :)
     if(first.arrivalTime < second.arrivalTime)
     {
        return true;
     }
     else
     {
        return false;
     }
#else
     return first.arrivalTime < second.arrivalTime;
#endif
}


bool argParser( int argc, char *argv[], double& lambda, int& scheduleType, double& serviceTime, double& quantum )
{
    std::string parameter;
    bool goodArgs = false;

	if(argc >= 4 && argc <= 5) 
	{
		try
		{
			scheduleType = atoi(argv[1]);
			lambda = atoi(argv[2]);
			serviceTime = atof(argv[3]);
			if(argc > 4) // just in case it doesn't get specified, which is fine in non-robin cases
				quantum = atof(argv[4]);
			else
				quantum = 0.01;
			
			goodArgs = ( lambda >= 1 && lambda <= 30 )
				    && ( scheduleType >= 1 && scheduleType <= 5 )
				    && ( serviceTime > 0 )
				    && ( quantum > 0 );
		}
		catch(std::exception)
		{
			std::cerr << "Invalid argument(s)" << std::endl;
		}
	}

	if(!goodArgs)
	{
		std::cout << " Usage: "	<< argv[0] << " <scheduleType> <lambda> <averageServiceTime> [quantum]" << std::endl
		          << " Parameters" << std::endl
		          << "  scheduleType:     1...5" << std::endl
		          << "  lambda:           1...30" << std::endl
		          << "  avg service time: 0.06" << std::endl
		          << "  quantum:          (0.01, 0.05, or 0.1)" << std::endl;
	}

	return goodArgs;
}

void computeMetrics(metrics_t &metrics, eventQueue_t &doneQueue, double &timer, double &busyTime, int &readyQueueNum, int &readyQueuePings)
{
    //Calculating metrics :
    //            AverageTurnaround
    //            Total ThroughPut
    //            Average CPU utilization
    //            Average number of process in ready queue
    double dTimeFront = 0;
    double aTimeNext = 0;
    double tempTurnaround;
    for (eventQueue_t::iterator it = doneQueue.begin(); it != doneQueue.end(); ++it)
    {
        dTimeFront = it->departureTime;  
        aTimeNext = it->arrivalTime;    
        tempTurnaround = dTimeFront - aTimeNext;
        metrics.avgTurnaround += tempTurnaround;   
    }
    metrics.avgTurnaround /= doneQueue.size();
    metrics.throughPut = ((double )doneQueue.size()) / timer;
    metrics.cpuUtilization = busyTime / timer;
    metrics.avgProcessInQueue = readyQueueNum / readyQueuePings;   
}
